﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using NSubstitute;
using Phoenix.Frames;
using Phoenix.Framework;
using Phoenix.Tests.Controllers;
using Xunit;

namespace Phoenix.Tests.Navigation
{
    public class GenericControllerActionsTests
    {
        readonly ControllerActionContext currentContext;
        readonly ViewModelBase viewModel;
        readonly INavigationFrame targetFrame;

        public GenericControllerActionsTests()
        {
            targetFrame = Substitute.For<INavigationFrame>();
            currentContext = PhoenixObjectMother.CreateContext(new ControllerAction("TestController", "OrigAction", null));
            viewModel = new TestViewModel { Context = currentContext };
        }

        [Fact]
        public void navigate_to_action()
        {
            // arrange
            var requestBuilder = new InvokeActionBuilder<TestController>(viewModel);

            // act
            requestBuilder.InvokeAction("TestAction");

            // assert
            currentContext.Host.ActionInvoker
                .Received()
                .InvokeAction(Arg.Is(MatchingRequest(ControllerAction.CreateFrom("TestController.TestAction", "Phoenix.Tests"))));
        }

        [Fact]
        public void navigate_to_action_specifying_frame()
        {
            // arrange
            var requestBuilder = new InvokeActionBuilder<TestController>(viewModel);

            // act
            requestBuilder.InvokeAction(targetFrame, "TestAction");

            // assert
            currentContext.Host.ActionInvoker
                .Received()
                .InvokeAction(
                    Arg.Is(MatchingRequest(ControllerAction.CreateFrom("TestController.TestAction", "Phoenix.Tests"), null, targetFrame)));
        }

        [Fact]
        public void navigate_to_action_with_expression()
        {
            // arrange
            var requestBuilder = new InvokeActionBuilder<TestController>(viewModel);

            // act
            requestBuilder.InvokeAction(c => c.TestAction());

            // assert
            currentContext.Host.ActionInvoker
                .Received()
                .InvokeAction(
                    Arg.Is(MatchingRequest(ControllerAction.CreateFrom("TestController.TestAction", "Phoenix.Tests"))));
        }

        [Fact]
        public void navigate_to_action_with_expression_specifying_frame()
        {
            // arrange
            var frame = Substitute.For<INavigationFrame>();
            var requestBuilder = new InvokeActionBuilder<TestController>(viewModel);

            // act
            requestBuilder.InvokeAction(frame, c => c.TestAction());

            // assert
            currentContext.Host.ActionInvoker
                .Received()
                .InvokeAction(
                    Arg.Is(MatchingRequest(
                        ControllerAction.CreateFrom("TestController.TestAction", "Phoenix.Tests"), null, frame)));
        }

        [Fact]
        public void navigate_to_action_with_single_argument()
        {
            // arrange
            var requestBuilder = new InvokeActionBuilder<TestController>(viewModel);

            // act
            requestBuilder.InvokeAction("TestAction", new object[]{1});

            // assert
            currentContext.Host.ActionInvoker.Received()
                .InvokeAction(
                    Arg.Is(MatchingRequest(
                        ControllerAction.CreateFrom("TestController.TestAction", "Phoenix.Tests"),
                        c => ((int)c.Single().Value) == 1)));
        }

        [Fact]
        public void navigate_to_action_expression_with_single_argument()
        {
            // arrange
            var requestBuilder = new InvokeActionBuilder<TestController>(viewModel);

            // act
            requestBuilder.InvokeAction(c => c.TestAction(1));

            // assert
            currentContext.Host.ActionInvoker
                .Received()
                .InvokeAction(Arg.Is(MatchingRequest(ControllerAction.CreateFrom("TestController.TestAction", "Phoenix.Tests"), 
                a => ((int)a.Single().Value) == 1 && a.Single().Name == "arg")));
        }

        [Fact]
        public void navigate_to_action_with_single_argument_specifying_frame()
        {
            // arrange
            var frame = Substitute.For<INavigationFrame>();
            var requestBuilder = new InvokeActionBuilder<TestController>(viewModel);

            // act
            requestBuilder.InvokeAction(frame, "TestAction", new object[]{1});

            // assert
            currentContext.Host.ActionInvoker.Received()
                .InvokeAction(
                    Arg.Is(MatchingRequest(
                        ControllerAction.CreateFrom("TestController.TestAction", "Phoenix.Tests"),
                        c => ((int) c.Single().Value) == 1,
                        frame)));
        }

        [Fact]
        public void navigate_to_action_expression_with_single_argument_specifying_frame()
        {
            // arrange
            var frame = Substitute.For<INavigationFrame>();
            var requestBuilder = new InvokeActionBuilder<TestController>(viewModel);

            // act
            requestBuilder.InvokeAction(frame, c => c.TestAction(1));

            // assert
            currentContext.Host.ActionInvoker.Received()
                .InvokeAction(
                    Arg.Is(MatchingRequest(
                ControllerAction.CreateFrom("TestController.TestAction", "Phoenix.Tests"),
                c => ((int)c.Single().Value) == 1 && c.Single().Name == "arg",
                frame)));
        }

        [Fact]
        public void navigate_to_action_expression_with_two_arguments()
        {
            // arrange
            var requestBuilder = new InvokeActionBuilder<TestController>(viewModel);
            var emptyList = new List<string>();

            // act
            requestBuilder.InvokeAction(c => c.TestAction(1, emptyList));

            // assert
            currentContext.Host.ActionInvoker.Received()
                .InvokeAction(
                    Arg.Is(MatchingRequest(
                ControllerAction.CreateFrom("TestController.TestAction", "Phoenix.Tests"),
                c => ((int)c.First().Value) == 1 && c.First().Name == "arg" &&
                ((List<string>)c.ElementAt(1).Value) == emptyList && c.ElementAt(1).Name == "arg2")));
        }

        Expression<Predicate<InvokeActionRequest>> MatchingRequest(
            ControllerAction controllerAction,
            Predicate<IEnumerable<ControllerActionParameter>> parameterPredicate = null,
            INavigationFrame frame = null)
        {
            return c => c.CurrentContext == currentContext &&
                        c.Target == controllerAction &&
                        (parameterPredicate == null || parameterPredicate(c.Parameters)) &&
                        (frame == null || c.TargetFrame == frame);
        }
    }

    public class TestViewModel : ViewModelBase
    {
    }
}